﻿namespace DiffControl
{
    using System;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;

    public class ScrollableListView : ListView
    {
        private const uint ESB_DISABLE_BOTH = 3;
        private const uint ESB_ENABLE_BOTH = 0;
        private bool isManualScrolling = false;
        private const int MK_CONTROL = 8;
        private const int MK_LBUTTON = 1;
        private const int MK_MBUTTON = 0x10;
        private const int MK_RBUTTON = 2;
        private const int MK_SHIFT = 4;
        private const int MK_XBUTTON1 = 0x20;
        private const int MK_XBUTTON2 = 0x40;
        private int prevPosition = 0;
        private const uint SB_BOTH = 3;
        private const int SB_BOTTOM = 7;
        private const uint SB_CTL = 2;
        private const int SB_ENDSCROLL = 8;
        private const uint SB_HORZ = 0;
        private const int SB_LINEDOWN = 1;
        private const int SB_LINEUP = 0;
        private const int SB_PAGEDOWN = 3;
        private const int SB_PAGEUP = 2;
        private const int SB_THUMBPOSITION = 4;
        private const int SB_THUMBTRACK = 5;
        private const int SB_TOP = 6;
        private const uint SB_VERT = 1;
        private const int WM_HSCROLL = 0x114;
        private const int WM_MOUSEWHEEL = 0x20a;
        private const int WM_NCCALCSIZE = 0x83;
        private const int WM_PAINT = 15;
        private const int WM_SIZE = 5;
        private const int WM_VSCROLL = 0x115;

        public event ScrollEventHandler ScrollHorizontal;

        public event MouseEventHandler ScrollMouseWheel;

        public event ScrollEventHandler ScrollVertical;

        public ScrollableListView()
        {
            base.Click += new EventHandler(this.ScrollableListView_Click);
        }

        [DllImport("user32.dll")]
        public static extern bool EnableScrollBar(IntPtr hWnd, uint wSBflags, uint wArrows);
        private int getSBFromScrollEventType(ScrollEventType type)
        {
            switch (type)
            {
                case ScrollEventType.SmallDecrement:
                    return 0;

                case ScrollEventType.SmallIncrement:
                    return 1;

                case ScrollEventType.LargeDecrement:
                    return 2;

                case ScrollEventType.LargeIncrement:
                    return 3;

                case ScrollEventType.ThumbPosition:
                    return 4;

                case ScrollEventType.ThumbTrack:
                    return 5;

                case ScrollEventType.First:
                    return 6;

                case ScrollEventType.Last:
                    return 7;

                case ScrollEventType.EndScroll:
                    return 8;
            }
            return -1;
        }

        private ScrollEventType getScrollEventType(IntPtr wParam)
        {
            switch (LoWord((int) wParam))
            {
                case 0:
                    return ScrollEventType.SmallDecrement;

                case 1:
                    return ScrollEventType.SmallIncrement;

                case 2:
                    return ScrollEventType.LargeDecrement;

                case 3:
                    return ScrollEventType.LargeIncrement;

                case 4:
                    return ScrollEventType.ThumbPosition;

                case 5:
                    return ScrollEventType.ThumbTrack;

                case 6:
                    return ScrollEventType.First;

                case 7:
                    return ScrollEventType.Last;

                case 8:
                    return ScrollEventType.EndScroll;
            }
            return ScrollEventType.EndScroll;
        }

        [DllImport("user32.dll")]
        public static extern int GetScrollPos(IntPtr hWnd, int nBar);
        [DllImport("user32.dll", CharSet=CharSet.Auto)]
        public static extern int GetSystemMetrics(int code);
        private static int HiWord(int number)
        {
            if ((number & 0x80000000L) == 0x80000000L)
            {
                return (number >> 0x10);
            }
            return ((number >> 0x10) & 0xffff);
        }

        [DllImport("user32.dll")]
        private static extern int HIWORD(IntPtr wParam);
        private static int LoWord(int number)
        {
            return (number & 0xffff);
        }

        private static int MakeLong(int LoWord, int HiWord)
        {
            return ((HiWord << 0x10) | (LoWord & 0xffff));
        }

        private static IntPtr MakeLParam(int LoWord, int HiWord)
        {
            return (IntPtr) ((HiWord << 0x10) | (LoWord & 0xffff));
        }

        public void performScrollHorizontal(ScrollEventType type)
        {
            this.isManualScrolling = true;
            int num = this.getSBFromScrollEventType(type);
            if (num != -1)
            {
                SendMessage(base.Handle, 0x114, (UIntPtr) num, IntPtr.Zero);
            }
        }

        public void performScrollVertical(ScrollEventType type)
        {
            this.isManualScrolling = true;
            this.prevPosition = GetScrollPos(base.Handle, 1);
            int num = this.getSBFromScrollEventType(type);
            if (num != -1)
            {
                SendMessage(base.Handle, 0x115, (UIntPtr) num, IntPtr.Zero);
            }
        }

        private void ScrollableListView_Click(object sender, EventArgs e)
        {
            base.Focus();
        }

        [DllImport("user32.dll")]
        private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, UIntPtr wParam, IntPtr lParam);
        [DllImport("user32.dll")]
        public static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);
        [DllImport("user32.dll")]
        public static extern int SetScrollRange(IntPtr hWnd, int nBar, int nMinPos, int nMaxPos, bool bRedraw);
        [DllImport("user32.dll")]
        public static extern bool ShowScrollBar(IntPtr hWnd, int wBar, bool bShow);
        protected override void WndProc(ref Message msg)
        {
            base.WndProc(ref msg);
            if (msg.HWnd == base.Handle)
            {
                switch (msg.Msg)
                {
                    case 0x114:
                        try
                        {
                            if (!this.isManualScrolling && (this.ScrollHorizontal != null))
                            {
                                ScrollEventArgs e = new ScrollEventArgs(this.getScrollEventType(msg.WParam), GetScrollPos(base.Handle, 0));
                                this.ScrollHorizontal(this, e);
                            }
                            else
                            {
                                this.isManualScrolling = false;
                            }
                        }
                        catch (Exception)
                        {
                        }
                        break;

                    case 0x115:
                        try
                        {
                            ScrollEventArgs args2;
                            if (this.isManualScrolling || (this.ScrollVertical == null))
                            {
                                goto Label_023B;
                            }
                            ScrollEventType type = this.getScrollEventType(msg.WParam);
                            int scrollPos = GetScrollPos(base.Handle, 1);
                            switch (type)
                            {
                                case ScrollEventType.ThumbPosition:
                                    goto Label_0231;

                                case ScrollEventType.ThumbTrack:
                                    if (scrollPos <= this.prevPosition)
                                    {
                                        break;
                                    }
                                    lock (this)
                                    {
                                        for (int i = this.prevPosition; i < scrollPos; i++)
                                        {
                                            args2 = new ScrollEventArgs(ScrollEventType.SmallIncrement, scrollPos);
                                            this.ScrollVertical(this, args2);
                                        }
                                    }
                                    goto Label_0231;

                                default:
                                    args2 = new ScrollEventArgs(type, scrollPos);
                                    this.ScrollVertical(this, args2);
                                    goto Label_0231;
                            }
                            if (scrollPos < this.prevPosition)
                            {
                                lock (this)
                                {
                                    for (int j = this.prevPosition; j > scrollPos; j--)
                                    {
                                        args2 = new ScrollEventArgs(ScrollEventType.SmallDecrement, scrollPos);
                                        this.ScrollVertical(this, args2);
                                    }
                                }
                            }
                        Label_0231:
                            this.prevPosition = scrollPos;
                            break;
                        Label_023B:
                            this.isManualScrolling = false;
                        }
                        catch (Exception)
                        {
                        }
                        break;

                    case 0x20a:
                        try
                        {
                            if (!this.isManualScrolling && (this.ScrollVertical != null))
                            {
                                MouseButtons left;
                                int delta = HiWord((int) msg.WParam);
                                int y = HiWord((int) msg.LParam);
                                int x = LoWord((int) msg.LParam);
                                switch (LoWord((int) msg.WParam))
                                {
                                    case 0x20:
                                        left = MouseButtons.XButton1;
                                        break;

                                    case 0x40:
                                        left = MouseButtons.XButton2;
                                        break;

                                    case 1:
                                        left = MouseButtons.Left;
                                        break;

                                    case 2:
                                        left = MouseButtons.Right;
                                        break;

                                    case 0x10:
                                        left = MouseButtons.Middle;
                                        break;

                                    default:
                                        left = MouseButtons.None;
                                        break;
                                }
                                MouseEventArgs args = new MouseEventArgs(left, 1, x, y, delta);
                                this.ScrollMouseWheel(this, args);
                                break;
                            }
                            this.isManualScrolling = false;
                        }
                        catch (Exception)
                        {
                        }
                        break;
                }
            }
        }

        public int AutoScrollHPos
        {
            get
            {
                return GetScrollPos(base.Handle, 0);
            }
            set
            {
                SetScrollPos(base.Handle, 0, value, true);
            }
        }

        public int AutoScrollVPos
        {
            get
            {
                return GetScrollPos(base.Handle, 1);
            }
            set
            {
                SetScrollPos(base.Handle, 1, value, true);
            }
        }
    }
}

